using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Magiq.Behaviors;
using Magiq.Delete;
using Magiq.Insert;
using Magiq.Support;
using Magiq.Update;

namespace Magiq
{
    public class MagiqOperations<T> : IMagiq<T>, IUpdateProvider<T> where T : class
    {
        private static readonly MethodInfo[] SelectMethods = typeof(Enumerable).GetMethods()
            .Where(x => x.Name == "Select")
            .Union(typeof(Queryable).GetMethods()
                       .Where(x => x.Name == "Select"))
            .ToArray();

        private readonly IMagiqBehavior behaviorChain;
        private readonly IQueryable<T> collection;

        public MagiqOperations(IQueryable<T> collection, IMagiqBehavior behaviorChain)
        {
            this.collection = collection;
            this.behaviorChain = behaviorChain;
        }

        #region IMagiq<T> Members

        public IUpdate<T> Set<TReturn>(Expression<Func<T, TReturn>> property, TReturn value)
        {
            return new Update<T>(collection, behaviorChain).Set(property, value);
        }

        public IUpdate<T> Set<TReturn>(Expression<Func<T, TReturn>> property, Expression<Func<T, TReturn>> value)
        {
            return new Update<T>(collection, behaviorChain).Set(property, value);
        }

        public int Delete()
        {
            return new Delete<T>(collection, behaviorChain).Execute();
        }

        public int Delete(Expression<Func<T, bool>> where)
        {
            return collection.Where(where).Delete();
        }

        public IMagiq<T> Using(IMagiqBehavior behavior)
        {
            behaviorChain.NextBehavior = behavior;
            return this;
        }
        public int Insert<TNew>(Expression<Func<IEnumerable<TNew>>> expression) where TNew : T
        {
            var call = expression.Body as MethodCallExpression;
            LambdaExpression insertExpression;

            if (call != null && call.Method.GetGenericMethodDefinition().In(SelectMethods))
            {
                var arg = call.Arguments[1];
                if (arg is UnaryExpression)
                    arg = ((UnaryExpression)arg).Operand;
                if (!(arg is LambdaExpression))
                    throw new InvalidOperationException("Insert using expressions should contain lambda expressions and not variables or method calls.");

                insertExpression = (LambdaExpression)arg;
                if (insertExpression.Parameters.Count == 3)
                    throw new InvalidOperationException(
                        "Select(Expression<Func<TSource,int,TResult>> selector) override is not supported.)");
            }
            else
            {
                var parameter = Expression.Parameter(typeof(TNew), "x");
                insertExpression = Expression.Lambda(parameter, parameter);
            }
            var current = call.Arguments[0];
            var expressions = new List<Expression> { call.Arguments[0] };

            while (current != null)
            {
                if (current is MemberExpression)
                {
                    current = ((MemberExpression)current).Expression;
                    expressions.Add(current);
                }
                else if (current is MethodCallExpression)
                {
                    var arguments = ((MethodCallExpression)current).Arguments;
                    if (arguments.Count > 0)
                    {
                        current = ((MethodCallExpression)current).Arguments[0];
                        expressions.Add(current);
                    }
                    else
                    {
                        current = null;
                    }
                }
                else
                {
                    current = null;
                }
            }

            Expression sourceExpression;

            if (expressions.Count == 1)
                sourceExpression = expressions.Single();
            else
                sourceExpression = expressions[expressions.Count - 2];

            var parameterType = sourceExpression.Type;

            var sourceParameter = Expression.Parameter(parameterType, "p");
            var rest = new ExpressionChanger(sourceExpression, sourceParameter).Visit(call.Arguments[0]);
            Type returnType;

            if (rest is MemberExpression)
            {
                var member = ((MemberExpression)rest).Member;
                returnType = member is PropertyInfo
                                 ? ((PropertyInfo)member).PropertyType
                                 : ((FieldInfo)member).FieldType;
            }
            else if (rest is ParameterExpression)
            {
                returnType = rest.Type;
            }
            else
            {
                returnType = ((MethodCallExpression)rest).Method.ReturnType;
            }
            var sourceCollectionItemType = insertExpression.Parameters.First().Type;

            var enumerable = typeof(IEnumerable<>).MakeGenericType(sourceCollectionItemType);

            if (returnType != enumerable)
                rest = Expression.Convert(rest, enumerable);

            var command =
                (IInsertCommand)
                Activator.CreateInstance(
                    typeof(InsertCommand<,,,>).MakeGenericType(sourceParameter.Type, sourceCollectionItemType, typeof(T), typeof(TNew)),
                    Expression.Lambda(rest, sourceParameter), insertExpression, sourceExpression, collection, behaviorChain);
            return command.Execute();

        }

        public int Insert<TNew>(IQueryable<TNew> source) where TNew : T
        {
            var expression = Expression.Lambda<Func<IEnumerable<TNew>>>(source.Expression);
            return Insert(expression);
        }

        public IMagiq<T> Using<TBehavior>() where TBehavior : IMagiqBehavior, new()
        {
            return Using<TBehavior>(x => { });
        }

        public IMagiq<T> Using<TBehavior>(Action<TBehavior> configureBehavior) where TBehavior : IMagiqBehavior, new()
        {
            var behavior = new TBehavior();
            configureBehavior(behavior);
            behaviorChain.NextBehavior = behavior;
            return this;
        }

        #endregion

        Update<T> IUpdateProvider<T>.GetUpdate()
        {
            return new Update<T>(collection, behaviorChain);
        }
    }

    public interface IInsertCommand
    {
        int Execute();
    }

    public class InsertCommand<TSource, TSourceCollection, TDestination, TNew> : IInsertCommand
        where TSource : class
        where TDestination : class
        where TNew : class, TDestination, new()
    {
        private readonly Expression<Func<TSource, IEnumerable<TSourceCollection>>> sourceCollectionExpression;
        private readonly Expression<Func<TSourceCollection, TNew>> insertExpression;
        private readonly Expression sourceExpression;
        private readonly IQueryable<TDestination> destination;
        private readonly MagiqBehavior behaviorChain;

        public InsertCommand(Expression<Func<TSource, IEnumerable<TSourceCollection>>> sourceCollectionExpression, Expression<Func<TSourceCollection, TNew>> insertExpression, Expression sourceExpression, IQueryable<TDestination> destination, MagiqBehavior behaviorChain)
        {
            this.sourceCollectionExpression = sourceCollectionExpression;
            this.behaviorChain = behaviorChain;
            this.insertExpression = insertExpression;
            this.sourceExpression = sourceExpression;
            this.destination = destination;
        }

        public int Execute()
        {
            var split = ExpressionSplit<TSource, TSourceCollection>.From(sourceCollectionExpression);

            var source = Expression.Lambda<Func<TSource>>(sourceExpression).Compile()();

            var query = source.Query(split.PropertyExpression);

            if (split.WhereExpression != null)
                query = query.Where(split.WhereExpression);
            //return 3
            return new InsertBuilder<TSourceCollection, TDestination>(query, destination, behaviorChain).Insert(insertExpression);
        }
    }
}